//---------------------------------------------------------------------
// File: OrchestrationConductorExStep.cs
// 
// Summary: Replaces the orchestrationconductor step with code that uses the object model
//
// Copyright (c) http://bizunitextensions.codeplex.com. All rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//---------------------------------------------------------------------

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Xml;
using BizUnit.BizUnitOM;
using Microsoft.BizTalk.ExplorerOM;

namespace BizUnit.Extensions.BizTalk
{
    using Enumerations;
    using Helpers;

    /// <summary>
    /// The OrchestrationConductorExStep may be used to stop/start an orchestration.
    /// This replaces the out of the box step with code that uses the OM.
    /// </summary>
    /// 
    /// <remarks>
    /// The following shows an example of the Xml representation of this test step.
    /// 
    /// <code escaped="true">
    /// <TestStep assemblyPath="BizUnit.Extensions.BizTalk.dll" typeName="BizUnit.Extensions.BizTalk.OrchestrationConductorExStep">
    ///		<DelayForCompletion>5</DelayForCompletion> <!-- Optional, seconds to delay for this step to complete -->
    ///		<AssemblyName>BizUnitTest.Process</AssemblyName>
    ///		<OrchestrationName>BizUnitTest.Process.SubmitToLedger</OrchestrationName>
    ///		<Action>Start</Action>
    /// </TestStep>
    /// </code>
    ///	<list type="table">
    ///		<listheader>
    ///			<term>Tag</term>
    ///			<description>Description</description>
    ///		</listheader>
    ///		<item>
    ///			<term>DelayForCompletion</term>
    ///			<description>The delay before executing the step <para>(optional)</para></description>
    ///		</item>
    ///		<item>
    ///			<term>AssemblyName</term>
    ///			<description>The name of the assembly containing the orchestration, e.g. BizUnitTest.Process</description>
    ///		</item>
    ///		<item>
    ///			<term>OrchestrationName</term>
    ///			<description>The name of the orchestration to start/stop</description>
    ///		</item>
    ///		<item>
    ///			<term>Action</term>
    ///			<description>Start|Stop</description>
    ///		</item>
    ///	</list>
    ///	</remarks>	

    public class OrchestrationConductorExStep : ITestStep, ITestStepOM
    {
        /// <summary>
        /// Enmerator for optional actions to be applied to the current instances when the 
        /// OrchestrationAction is applied. For example, if an enlisted orchestration is started
        /// an additional flag (AutoResumeSuspendedInstances) can be specified. But if this is not
        /// explicitly set it will default to false.
        /// </summary>
        public enum InstanceControlAction
        {
            /// <summary>
            /// Indicates that a default action should be applied that corresponds to the 
            /// OrchestrationAction that has been set.
            /// </summary>
            Default,
            /// <summary>
            /// Gets or sets the flag to specify whether to resume all suspended instances 
            /// automatically when the user changes the status of the orchestration 
            /// from Enlisted to Started. If not specified then default is FALSE.
            /// </summary>
            AutoResumeSuspendedInstances,

            /// <summary>
            /// Gets or sets the flag to specify whether to suspend all running instances 
            /// automatically when the user changes the status of the orchestration 
            /// from Started to Enlisted. If not specified then default is FALSE.
            /// </summary>
            AutoSuspendRunningInstances,

            /// <summary>
            /// Gets or sets the flag to specify whether to auto terminate 
            /// instances when the user unenlists an orchestration. If not specified then default is FALSE.
            /// </summary>
            AutoTerminateInstances

        }
        //private variables
        private BtsCatalogExplorer root;
        private IBizTalkApplication appInstance;

        private IBtsOrchestration2 orchInstance;
        private bool useApplication = false;

        private string applicationName;
        private string connectionString;
        private string assemblyName;

        private string orchestrationName;
        private OrchestrationAction action;
        private InstanceControlAction instanceAction;

        private int delayForCompletion;

        public string ApplicationName
        {
            get
            {
                return applicationName;
            }
            set
            {
                applicationName = value;
            }
        }
        public string AssemblyName
        {
            get
            {
                return assemblyName;
            }
            set
            {
                assemblyName = value;
            }
        }
        public string OrchestrationName
        {
            get
            {
                return orchestrationName;
            }
            set
            {
                orchestrationName = value;
            }
        }
        public string ConnectionString
        {
            get
            {
                return connectionString;
            }
            set
            {
                connectionString = value;
            }
        }
        public OrchestrationAction Action
        {
            get
            {
                return action;
            }
            set
            {
                action = value;
            }
        }
        public InstanceControlAction InstanceAction
        {
            get
            {
                return instanceAction;
            }
            set
            {
                instanceAction = value;
            }
        }
        public int DelayForCompletion
        {
            get
            {
                return delayForCompletion;
            }
            set
            {
                delayForCompletion = value;
            }
        }
        /// <summary>
        /// Enumerator for the action to apply on the orchestration
        /// </summary>


        /// <summary>
        /// ITestStep.Execute() implementation
        /// </summary>
        /// <param name='testConfig'>The Xml fragment containing the configuration for this test step</param>
        /// <param name='context'>The context for the test, this holds state that is passed beteen tests</param>
        public void Execute(XmlNode testConfig, Context context)
        {
            ApplicationName = context.ReadConfigAsString(testConfig, "ApplicationName", true);
            AssemblyName = context.ReadConfigAsString(testConfig, "AssemblyName", true);
            ConnectionString = context.ReadConfigAsString(testConfig, "ConnectionString", true);
            OrchestrationName = context.ReadConfigAsString(testConfig, "OrchestrationName");

            if (!String.IsNullOrEmpty(context.ReadConfigAsString(testConfig, "Action")))
            {
                Action = (OrchestrationAction)Enum.Parse(typeof(OrchestrationAction), context.ReadConfigAsString(testConfig, "Action"), true);
            }

            if (!String.IsNullOrEmpty(context.ReadConfigAsString(testConfig, "InstanceAction", true)))
            {
                InstanceAction = (InstanceControlAction)Enum.Parse(typeof(OrchestrationAction), context.ReadConfigAsString(testConfig, "InstanceAction", true), true);
            }


            DelayForCompletion = context.ReadConfigAsInt32(testConfig, "DelayForCompletion", true);
            Execute(context);

        }

        #region ITestStepOM Members

        public void Execute(Context context)
        {


            Validate(context);

            //Set some default actions

            try
            {

                ControlOrchestration(context);

                // Delay if necessary to allow the orchestration to start/stop

                if (DelayForCompletion > 0)
                {
                    context.LogInfo("Waiting for {0} seconds before recommencing testing.", DelayForCompletion);
                    Thread.Sleep(DelayForCompletion * 1000);
                }

            }
            catch (COMException e)
            {
                context.LogException(e);
                throw e;
            }
        }

        public void Validate(Context context)
        {
            if (String.IsNullOrEmpty(ApplicationName) && String.IsNullOrEmpty(AssemblyName))
            {
                InvalidOperationException iox = new InvalidOperationException("Both Application and Assembly Name cannot be empty");
                context.LogException(iox);
                throw iox;
            }
            if (!String.IsNullOrEmpty(ApplicationName))
            {
                context.LogInfo("Application will be used as container for getting the Orch");
                useApplication = true;
            }


            ArgumentValidation.CheckForEmptyString(OrchestrationName, "Orchestration Name");
            ArgumentValidation.CheckForEmptyString(Action.ToString(), "Orchestration Action");



        }

        #endregion

        #region "main private methods"
        /// <summary>
        /// Starts orchestration with given name and assembly
        /// </summary>
        /// <param name="assemblyName">The name of the assembly</param>
        /// <param name="orchestrationName">The name of the orchestration</param>
        /// <param name="action">OrchestrationAction enumerator</param>
        /// <param name="connectionString">connection string to the BTS repository</param>
        /// <param name="context">global context variable</param>
        private void ControlOrchestration(Context context)
        {
            switch (action)
            {
                case OrchestrationAction.Start:
                    Start(context);
                    break;

                case OrchestrationAction.Enlist:
                    Enlist(context);
                    break;

                case OrchestrationAction.Unenlist:
                    Unenlist(context);
                    break;

                case OrchestrationAction.Stop:
                    Stop(context);
                    break;

                default:
                    context.LogError("Required Action not recognised");
                    break;

            }
        }

        private string QueryStatus()
        {
            SetInstance();
            return orchInstance.Status.ToString();


        }


        private void Start(Context context)
        {
            if (QueryStatus().Equals("Started"))
            {
                context.LogInfo("Orchestration is already started");
                return;
            }
            try
            {
                SetInstance();
                if ((InstanceAction == InstanceControlAction.Default) || (InstanceAction == InstanceControlAction.AutoResumeSuspendedInstances))
                {
                    orchInstance.AutoResumeSuspendedInstances = true;
                }
                else
                {
                    orchInstance.AutoResumeSuspendedInstances = false;
                }
                orchInstance.Status = OrchestrationStatus.Started;
                //Try to commit the changes made so far. If it fails, roll-back 
                //all the changes.
                root.SaveChanges();
                LogSuccess(context);
            }
            catch (Exception e)
            {
                LogFailure(context, e);
                throw e;
            }

        }

        private void Stop(Context context)
        {
            if (QueryStatus().Equals("Stopped"))
            {
                context.LogInfo("Orchestration is already stopped");
                return;
            }
            try
            {
                SetInstance();
                //No additional parameters are needed for stopping. It essentially rolls the 
                //status back to Enlisted
                orchInstance.Status = OrchestrationStatus.Enlisted;
                //Try to commit the changes made so far. If it fails, roll-back 
                //all the changes.
                root.SaveChanges();
                LogSuccess(context);
            }
            catch (Exception e)
            {
                LogFailure(context, e);
                throw e;
            }

        }
        private void Enlist(Context context)
        {
            if (QueryStatus().Equals("Enlisted"))
            {
                context.LogInfo("Orchestration is already enlisted");
                return;
            }
            try
            {
                SetInstance();
                if ((InstanceAction == InstanceControlAction.Default) || (InstanceAction == InstanceControlAction.AutoSuspendRunningInstances))
                {
                    orchInstance.AutoSuspendRunningInstances = true;
                }
                else
                {
                    orchInstance.AutoSuspendRunningInstances = false;
                }
                orchInstance.Status = OrchestrationStatus.Enlisted;
                //Try to commit the changes made so far. If it fails, roll-back 
                //all the changes.
                root.SaveChanges();
                LogSuccess(context);
            }
            catch (Exception e)
            {
                LogFailure(context, e);
                throw e;
            }

        }

        private void Unenlist(Context context)
        {
            if (QueryStatus().Equals("Unenlisted"))
            {
                context.LogInfo("Orchestration is already unenlisted");
                return;
            }
            try
            {
                SetInstance();
                if ((InstanceAction == InstanceControlAction.Default) || (InstanceAction == InstanceControlAction.AutoTerminateInstances))
                {
                    orchInstance.AutoTerminateInstances = true;
                }
                else
                {
                    orchInstance.AutoTerminateInstances = false;
                }
                orchInstance.Status = OrchestrationStatus.Unenlisted;
                //Try to commit the changes made so far. If it fails, roll-back 
                //all the changes.
                root.SaveChanges();
                LogSuccess(context);
            }
            catch (Exception e)
            {
                LogFailure(context, e);
                throw e;
            }

        }

        #endregion

        #region "private helpers"


        private void SetInstance()
        {
            root = BizTalkStepHelper.GetRoot(ConnectionString);

            //if the applicationName is provided then it will take it from the application
            if (useApplication)
            {

                appInstance = BizTalkStepHelper.GetApplication(ApplicationName, root);
                orchInstance = BizTalkStepHelper.GetOrchestration(appInstance, OrchestrationName);

            }
            else
            {
                orchInstance = root.Assemblies[assemblyName].Orchestrations[orchestrationName];
            }

        }
        private void LogSuccess(Context context)
        {
            context.LogInfo(String.Format("Action  {0} has been applied to Orchestration {1} from Assembly {2} has been STARTED", Action.ToString(), OrchestrationName, AssemblyName));
        }
        private void LogFailure(Context context, Exception e)
        {
            context.LogInfo(String.Format("ERROR. Action {0} could not be applied to Orchestration {1} from Assembly {2}", Action.ToString(), OrchestrationName, AssemblyName));
            context.LogException(e);
        }

        #endregion

    }
}
